import codecs
import os, django

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "devops.settings")
django.setup()

from host.models import Host, HostVideo
from devops.settings import BASE_DIR
import datetime
import socket
from threading import Thread
import paramiko
from channels.generic.websocket import WebsocketConsumer
import json
import time
from multiprocessing import Queue


class Writer:
    def __init__(self, path, header=None, mode='w', buffering=1):
        self.path = path
        self.header = header
        self.mode = mode
        self.buffering = buffering
        self.stdout_decoder = codecs.getincrementaldecoder('UTF-8')('replace')

    def __enter__(self):
        self.file = open(self.path, mode=self.mode, buffering=self.buffering)
        if self.header:
            self.__write_line(self.header)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()

    def write_event(self, ts, etype=None, data=None):
        if etype is None:
            ts, etype, data = ts
        ts = round(ts, 6)
        if etype == 'o':
            if type(data) == str:
                data = data.encode(encoding='utf-8', errors='strict')
            text = self.stdout_decoder.decode(data)
            self.__write_line([ts, etype, text])

    def __write_line(self, obj):
        line = json.dumps(obj, ensure_ascii=False, indent=None, separators=(', ', ': '))
        self.file.write(line + '\n')


def write_json_lines_from_queue(path, header, mode, queue):
    with Writer(path=path, header=header, mode=mode, buffering=1) as w:
        for event in iter(queue.get, None):
            w.write_event(event)


class PlayerData:
    def __init__(self, pk, operator):
        # 生成初始化时间
        self.time = time.time()
        self.queue = Queue()
        # 生成录像名
        self._videotapeName()
        # sql生成
        self.obj = HostVideo.objects.create(path='{}.cast'.format(int(self.time)), host=pk, operator=operator)

    def _videotapeName(self):
        path = os.path.join(str(BASE_DIR) + '/videotape/')
        if not os.path.isdir(path):
            os.makedirs(path)
        # self.path = open('{}/{}.cast'.format(path, int(self.time)), 'a')
        # 生成头信息
        header = {"version": 2, "width": 400, "height": 30, "timestamp": int(self.time),
                  "env": {"SHELL": "/bin/bash", "TERM": "xterm"}}
        self.writer = Thread(target=write_json_lines_from_queue,
                             args=('{}/{}.cast'.format(path, int(self.time)), header, 'a', self.queue))
        self.writer.start()

    # videotapeData 生成记录信息
    def videotapeData(self, data):
        # [时间戳,模式,"记录信息"]
        # 只会是i或o之间的一种，分别表示stdin标准输入或stdout标准输出
        # [0.084666, "o", "\u001b]0;root@localhost:~\u0007\u001b[?1034h[root@localhost ~]# "]
        # iodata = [time.time() - self.time, 'o', data]
        self.queue.put((time.time() - self.time, 'o', data))

    def close(self):
        HostVideo.objects.filter(pk=self.obj.pk).update(endtime=datetime.datetime.now())
        self.queue.put(None)
        self.writer.join()


class SSHBridge:

    def __init__(self, websocket):
        self.websocket = websocket

    def _send(self, data: dict):
        self.websocket.send(json.dumps(data))

    # 套接字生成
    def socketlink(self, hostname, port):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.connect((hostname, port))
        except Exception as f:
            self._send({'code': 1050, 'data': str(f)})
            return False

    # paramiko套接字连接与验证
    def sshlink(self, pk, operator, username, password):
        try:
            ssh = paramiko.Transport(self.sock)
            ssh.start_client()
            ssh.auth_password(username, password)
        except Exception as f:
            self._send({'code': 1050, 'data': str(f)})
            return False
        self.chan = ssh.open_session()
        self.chan.get_pty(term='xterm')
        self.chan.invoke_shell()
        self.vido = PlayerData(pk=pk, operator=operator)
        for i in range(2):
            data = self.chan.recv(1024).decode('utf-8')
            self._send({'code': 1000, 'data': data})
            self.vido.videotapeData(data)

    # 重新定义宽高
    def pty(self, w=80, h=32):
        self.chan.resize_pty(width=w, height=h)

    # 执行命令
    def _sshcmd(self, data):
        try:
            self.chan.send(data)
            # 如果远程进程已退出并返回退出，则返回true状态
            while not self.chan.exit_status_ready():
                data = self.chan.recv(1024).decode('utf-8', 'ignore')
                if len(data) != 0:
                    self.vido.videotapeData(data)
                    self._send({'code': 1000, 'data': data})
                else:
                    break
        except Exception as f:
            self._send({'code': 1007, 'data': '关闭'})
            self.close()

    # 断开连接
    def close(self):
        self.vido.close()
        self.chan.close()
        self.websocket.close()

    # 开启一个线程专门来做 IO堵塞
    def sshsend(self, data):
        Thread(target=self._sshcmd, args=(data,)).start()


class WebSshSocket(WebsocketConsumer):
    def connect(self):
        self.pk = self.scope['url_route']['kwargs']['host']
        self.operator = self.scope['url_route']['kwargs']['operator']
        _host = Host.objects.filter(pk=self.pk)
        if _host.first():
            _host = _host.first()
            self.accept()
            self.ssh = SSHBridge(websocket=self)
            self.ssh.socketlink(hostname=_host.ip, port=_host.port)
            self.ssh.sshlink(username=_host.username,
                             password=_host.pwd, pk=_host, operator=self.operator)

    def disconnect(self, code):
        try:
            self.ssh.close()
        except Exception as f:
            pass
        self.close()

    def receive(self, text_data):
        try:
            pty = json.loads(text_data)
            self.ssh.pty(w=pty.get('_W'), h=pty.get('_H'))
        except Exception as f:
            self.ssh.sshsend(text_data)
